package com.smart.service.book;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import javax.persistence.EntityManager;
import javax.persistence.EntityTransaction;

import com.smart.builder.xml.BuilderInfoBookHotelResponseType;
import com.smart.builder.xml.BuilderListBookHotelResponseType;
import com.smart.hotelsmart.BillingAddressType;
import com.smart.hotelsmart.BookHotelRequestType;
import com.smart.hotelsmart.BookHotelResponseType;
import com.smart.hotelsmart.BookRoomType;
import com.smart.hotelsmart.InfoBookHotelRequestType;
import com.smart.hotelsmart.InfoBookHotelResponseType;
import com.smart.hotelsmart.ListBookHotelRequestType;
import com.smart.hotelsmart.ListBookHotelResponseType;
import com.smart.hotelsmart.ListBookRoomType;
import com.smart.hotelsmart.PaymentType;
import com.smart.persistence.entity.BillingAddress;
import com.smart.persistence.entity.BookHotel;
import com.smart.persistence.entity.BookRoom;
import com.smart.persistence.entity.Card;
import com.smart.persistence.entity.Hotel;
import com.smart.persistence.entity.Payment;
import com.smart.persistence.entity.Room;
import com.smart.persistence.service.SmartBasicCrudDAO;
import com.smart.service.util.Logging;
import com.smart.service.util.UtilCard;
import com.smart.service.util.UtilDate;

public class BookHotelService {

	/** A Constant LOGGER. */
	private static final Logging LOGGER = Logging
			.getLogger(BookHotelService.class.getName());
	
	/*/# INICIO - EXERC. 2*/
	public InfoBookHotelResponseType getInfoBookHotel(InfoBookHotelRequestType body){
		LOGGER.debug("getInfoBookHotel "+body.getCodeBookHotel());
		EntityManager em = SmartBasicCrudDAO.getInstance().getEntityManagerDAO();
		
		BookHotel book = em.find(BookHotel.class, body.getCodeBookHotel());
		assert book != null : "id = " + body.getCodeBookHotel();
		InfoBookHotelResponseType info = new BuilderInfoBookHotelResponseType().createInfoBookHotelResponseType(book);
		
		return info;
		
	}
	
	public ListBookHotelResponseType getListBookHotel(ListBookHotelRequestType body){
		LOGGER.debug("getListBookHotel "+ body.getCustomer());
		
		ListBookHotelResponseType response = new BuilderListBookHotelResponseType().createListBookHotelResponseType(body);
		
		return response;
	}
	
	
	
	// FIM - EXERC. 2 #*/

	
	
	
	

	/**
	 * 
	 * @param body
	 * @return
	 */
	public BookHotelResponseType setBookHotel(BookHotelRequestType body) {
		LOGGER.debug("setBookHotel " + body);
		BookHotelResponseType response = new BookHotelResponseType();
		EntityManager em = SmartBasicCrudDAO.getInstance().getEntityManagerDAO();
		try {
			assert body.getPayment() != null : "body.getPayment = null";
			Card card = validateCard(em, body.getPayment());

			BillingAddress address = generateBillingAddress(body
					.getBillingAddress());

			Payment payment = generatePayment(card, body.getPayment()
					.getTotal(), address);

			ListBookRoomType listBookRoomType = body.getListBookRoom();
			assert listBookRoomType != null;
			List<BookRoomType> listBookRoom = listBookRoomType.getBookRoom();
			assert listBookRoom != null;
			assert !listBookRoom.isEmpty();
			Date checkOut = UtilDate.getDate(body.getDeparture());
			Date checkIn = UtilDate.getDate(body.getArrival());
			List<Room> listRoomAvailable = getListRoomAvailable(em ,listBookRoom
					.get(0).getCodeRoom(), checkIn, checkOut);
			List<BookRoom> booksRoom = generateListBookRoom(checkIn, checkOut,
					listRoomAvailable, listBookRoom);
			Hotel hotel = listRoomAvailable.get(0).getRoomType().getHotel();
			BookHotel bookHotel = generateBookHotel(hotel, body.getCustomer(), checkIn, checkOut);
			bookHotel = generateBookHotel(em, bookHotel, hotel, payment, booksRoom);
			response.setResponse(bookHotel.getId());
		} catch (RuntimeException rte) {
			LOGGER.error("erro: ", rte);
			throw rte;
		}finally {
			em.close();
		}
		return response;
	}

	private BookHotel generateBookHotel(EntityManager em,BookHotel bookHotel, Hotel hotel, Payment payment, List<BookRoom> booksRoom) {
		LOGGER.debug("generateBookHotel ");
		EntityTransaction et = em.getTransaction();
		try {
			et.begin();
			//
			em.persist(bookHotel);
			bookHotel = SmartBasicCrudDAO.getInstance()
					.consultaQuerySingle(em, BookHotel.class,
							BookHotel.findLastBookHotel, hotel.getId());
			//
			if (payment != null) {
				payment.setBookHotel(bookHotel);
				em.persist(payment);
			}
			//
			for (BookRoom bookRoom : booksRoom) {
				bookRoom.setBookHotel(bookHotel);
				em.persist(bookRoom);
			}
			//
			et.commit();
		} catch (Exception e) {
			et.rollback();
			e.printStackTrace();
		} 
		bookHotel.setPayment(payment);
		bookHotel.setBooksRoom(booksRoom);
		return bookHotel;
	}

	private BookHotel generateBookHotel(Hotel hotel, String customer, Date checkIn, Date checkOut) {
		BookHotel bookHotel = new BookHotel();
		bookHotel.setHotel(hotel);
		bookHotel.setCustomer(customer);
		bookHotel.setCheckIn(checkIn);
		bookHotel.setCheckOut(checkOut);
		/*/# INICIO - EXERC. 2*/
		bookHotel.setDate(new Date());
		// FIM - EXERC. 2 #*/
		return bookHotel;
	}

	private List<BookRoom> generateListBookRoom(Date checkIn, Date checkOut,
			List<Room> listRoomAvailable, List<BookRoomType> listBookRoom) {
		LOGGER.debug("generateListBookRoom ");
		assert checkIn != null;
		assert checkOut != null;
		assert listBookRoom != null;
		List<BookRoom> retornoListBookRoom = new ArrayList<BookRoom>();
		assert listRoomAvailable != null : "listRoomAvailable = null";
		assert listBookRoom.size() <= listRoomAvailable.size() : "listBookRoom size(" + listBookRoom.size() +") > listRoomAvailable size (" + listRoomAvailable.size() +")";
			for (int j = 0; j < listBookRoom.size(); j++) {
				listRoomAvailable.get(j).getRoomType();
				BookRoomType bookRoomType = listBookRoom.get(j);
				Room room = listRoomAvailable.get(j);
				int maxAmountAdults = room.getRoomType().getMaxAmountAdults();
				int maxAmountChildren = room.getRoomType()
						.getMaxAmountChildren();
				if (bookRoomType.getAmountChildren() > maxAmountChildren) {
					throw new RuntimeException("erro7a");
				}
				if (bookRoomType.getAmountAdults() > maxAmountAdults) {
					throw new RuntimeException("erro7b");
				}

				BookRoom book = generateBookRoom(bookRoomType, checkIn,
						checkOut, room);
				retornoListBookRoom.add(book);
			}
		return retornoListBookRoom;
	}

	private BookRoom generateBookRoom(BookRoomType bookRoomType, Date checkIn,
			Date checkOut, Room room) {
		LOGGER.debug("generateBookRoom " + bookRoomType + checkIn + checkOut
				+ room);
		assert bookRoomType != null;
		assert room != null;
		BookRoom bookRoom = new BookRoom();
		bookRoom.setAmountAdults(bookRoomType.getAmountAdults());
		bookRoom.setAmountchildren(bookRoomType.getAmountChildren());
		bookRoom.setCheckIn(checkIn);
		bookRoom.setCheckOut(checkOut);
		bookRoom.setHostFirstName(bookRoomType.getFirstNameHost());
		bookRoom.setHostLastName(bookRoomType.getLastNameHost());
		bookRoom.setRoom(room);
		return bookRoom;
	}

	private List<Room> getListRoomAvailable(EntityManager em, int codRoomType, Date checkIn,
			Date checkOut) {
		assert checkIn != null;
		assert checkOut != null;
		assert codRoomType != 0;
		LOGGER.debug("getListRoomAvailable " + codRoomType + " - "
				+ UtilDate.formatDate(checkIn) + " - "
				+ UtilDate.formatDate(checkOut));
		List<Room> list = SmartBasicCrudDAO.getInstance().consultaQueryList(em,
				Room.class, Room.findAvailableInPeriodByCodRoomType, codRoomType,
				checkIn, checkOut);
		return list;
	}

	private BillingAddress generateBillingAddress(BillingAddressType addressT) {
		LOGGER.debug("generateBillingAddress " + addressT);
		assert addressT != null;
		BillingAddress address = new BillingAddress();
		address.setCity(addressT.getCity());
		address.setCountry(addressT.getCountry());
		address.setState(addressT.getState());
		address.setStreetAddress(addressT.getStreetAddress());
		address.setZipCode(addressT.getZipCode());
		return address;

	}

	private Payment generatePayment(Card card, BigDecimal total,
			BillingAddress adress) {
		LOGGER.debug("generatePayment " + card + total + adress);
		Payment payment = new Payment();
		assert total != null;
		assert adress != null;
		payment.setTotal(total);
		payment.setAdress(adress);
		//INICIO - EXERC. 6 
		assert card != null; 
		payment.setCard(card); 
		//FIM - EXEC.
		return payment;
	}
	
	private Card validateCard(EntityManager em, PaymentType payment) {
		Card card = null;
		//INICIO - EXERC.6 
		LOGGER.debug("validateCard numeber " + payment.getCardNumber() + " type " + payment.getCardType()); 
	assert payment != null; 
	card = getCardByNumber(em, payment.getCardNumber()); 
	if (card == null) { 
		String retorno = new UtilCard().validate(payment.getCardType(), 
				String.valueOf(payment.getCardNumber()), 
				payment.getExpiryMonth() + "/" + payment.getExpiryYear()); 
		if( UtilCard.CARTAO_ERRO.equals(retorno)){ 
			throw new RuntimeException(UtilCard.CARTAO_ERRO + " number " + payment.getCardNumber() + " date = " 
			+ payment.getExpiryMonth() + "/" + payment.getExpiryYear() 
			+ " type = " + payment.getCardType()); 
		}
		
		card = new Card();
		card.setCardNumber(payment.getCardNumber());
		card.setCardType(payment.getCardType());
		card.setExpiryMonth(payment.getExpiryMonth());
		card.setExpiryYear(payment.getExpiryYear());
		card.setFirstName(payment.getFirstName());
		card.setLastName(payment.getLastName());
		card.setSecurityCode(payment.getSecurityCode());
		EntityTransaction et = null;
		try {
			et = em.getTransaction();
			et.begin();
			em.persist(card);
			em.flush();
			et.commit();
		} catch (Exception e) {
			if (et != null && et.isActive()) {
				et.rollback();
			}
			throw new RuntimeException(e);
		}
		card = getCardByNumber(em, payment.getCardNumber());
		assert card != null : "card = null";
		assert card.getId() != 0 : "card.id == 0";
	} else {
		if (!card.getCardType().equals(payment.getCardType())) {
			throw new RuntimeException("errol");
		}
		if (card.getExpiryMonth() != payment.getExpiryMonth()){
			throw new RuntimeException("erro2");
		}
		if (card.getExpiryYear() != payment.getExpiryYear()) { 
			throw new RuntimeException("erro3");
		}
		if (!card.getFirstName().equals(payment.getFirstName())) { 
			throw new RuntimeException("errc4");
		}
		if (!card.getLastName().equals(payment.getLastName())) { 
			throw new RuntimeException("erro5");
		}
		if (card.getSecurityCode() != payment.getSecurityCode()) { 
			throw new RuntimeException("erro6");
		}
	}
	//FIM - EXERC. 6 #/
		return card;
		
		
	}
	

	/**
	 * Get the entity card through your number
	 * 
	 * @param number
	 * @return
	 */
	// INICIO - EXERC. 6
	
		public Card getCardByNumber(EntityManager em, long number){ 
			LOGGER.debug("getCardByNumber " + number); 
			Card card = SmartBasicCrudDAO.getInstance().consultaQuerySingle(em,
					Card.class, Card.findByCardNumber, number);
		return card;
		}
		
		//FIM - EXERC. 6 #

}
